package com.keyware.kd.intercepts;

import com.github.pagehelper.Dialect;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @ClassName CommonLikeSqlIntercepts
 * @Description 对like进行统一化处理
 * @Author 李军荣
 * @Date 2020/8/11 14:21
 * @Version 1.0
 */
@Intercepts(
        {@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,
                ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,
                ResultHandler.class, CacheKey.class, BoundSql.class})
        })
public class CommonLikeSqlIntercepts implements Interceptor {

    private boolean closeConn = true;

    private String  dialect;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 修改SQL
        String sql = getSqlByInvocation(invocation);
        if (StringUtils.isBlank(sql)) {
            return invocation.proceed();
        }
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement)args[0];
        DataSource dataSource = ms.getConfiguration().getEnvironment().getDataSource();
        String url = getUrl(dataSource);
        if (dialect == null || dialect.equals("")) {
             dialect = Dialect.fromJdbcUrl(url);
            if (dialect == null || dialect.equals("")){
                return invocation.proceed();
            }
        }

        boolean checked = checkLikeSQL(sql);
        if (!checked) {
            return invocation.proceed();
        }

        if("mysql".equals(dialect)){
            if(sql.contains("CONCAT")){
                return invocation.proceed();
            }
        }else{
            if(sql.contains("||")){
                return invocation.proceed();
            }
        }

        BoundSql boundSql = ms.getBoundSql(args[1]);
        String sql2Reset = modifyLikeSql(sql);
        //resetSql2Invocation(ms, sql2Reset,boundSql);
        resetSql2Invocation(sql2Reset,boundSql);
        // resetSql2Invocation(args, sql2Reset);
        // 返回，继续执行
        return invocation.proceed();
    }


    public String getUrl(DataSource dataSource){
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
            return conn.getMetaData().getURL();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            if(conn != null){
                try {
                    if(closeConn){
                        conn.close();
                    }
                } catch (SQLException e) {
                }
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
         dialect = properties.getProperty("dialect") == null ? "" : properties.getProperty("dialect") ;
    }

    private boolean checkLikeSQL(String sql) {
        return sql.indexOf("like") != -1 || sql.indexOf("LIKE") != -1;
    }


    private String getSqlByInvocation(Invocation invocation) {
        final Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameterObject = args[1];
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        return boundSql.getSql();
    }


    private void resetSql2Invocation(String sql, BoundSql boundSql) throws Throwable {
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, sql);
    }

 /*  private void resetSql2Invocation(MappedStatement ms, String sql, BoundSql boundSql) throws Throwable {
        SqlSource newSqlSource = new StaticSqlSource(ms.getConfiguration(),sql,boundSql.getParameterMappings());
        Field field = ms.getClass().getDeclaredField("sqlSource");
        field.setAccessible(true);
        field.set(ms, newSqlSource);
    }*/

/*    private void resetSql2Invocation(Object[] args, String sql) throws Throwable {
        MappedStatement statement = (MappedStatement) args[0];
        Object parameterObject = args[1];
        BoundSql boundSql = statement.getBoundSql(parameterObject);
        MappedStatement newStatement = newMappedStatement(statement, new BoundSqlSqlSource(boundSql));
        MetaObject msObject = MetaObject.forObject(newStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory());
        msObject.setValue("sqlSource.boundSql.sql", sql);
        args[0] = newStatement;
    }*/


 /*   private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder =
                new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());

        return builder.build();
    }
*/


    /**
     * @param sql
     * @return java.lang.String
     * @description 修改SQL
     * @author 李军荣
     * @date 2020/8/11 9:13
     */
    private String modifyLikeSql(String sql) {
        if (!sql.toLowerCase().contains("like")) {
            return sql;
        }

        StringBuffer sb = new StringBuffer();
        String reg = "(?:LIKE|like)(?:\\s*)'%'(?:\\s*)\\|{1,}(?:\\s*)\\?(?:\\s*)\\|{1,}(?:\\s*)'%'(?:\\s*)";
        Pattern ptag = Pattern.compile(reg);
        Matcher mtag = ptag.matcher(sql);

        String like = "";
        if("mysql".equals(dialect)){
            like = " LIKE CONCAT('%',?,'%') ";
        }else{
            like = " LIKE '%'||?||'%' ";
        }

        while(mtag.find()){
            mtag.appendReplacement(sb, like);
        }
        mtag.appendTail(sb);
        return sb.toString();
    }


    // 定义一个内部辅助类，作用是包装sq
 /*   class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }*/

}
